import { httpError, IMiddleware, Middleware } from '@midwayjs/core'
import { JwtService } from '@midwayjs/jwt'
import { Context, NextFunction } from '@midwayjs/koa'
import { CacheManager } from '@ttou/midway-cache'

import { APP_MESSAGES, CACHE_KEYS } from '@/constant'
import { StateService } from '@/service'

@Middleware()
export class AuthenticationMiddleware
  implements IMiddleware<Context, NextFunction>
{
  static getName() {
    AuthenticationMiddleware.name
  }

  resolve() {
    return async (ctx: Context, next: NextFunction) => {
      const jwtService = await ctx.requestContext.getAsync(JwtService)
      const cacheManager = await ctx.requestContext.getAsync(CacheManager)
      const stateService = await ctx.requestContext.getAsync(StateService)

      // 请求头没有登录凭证
      if (!ctx.header['authorization']) {
        ctx.throw(
          new httpError.UnauthorizedError(APP_MESSAGES.AUTHORIZATION_NOT_EXIST)
        )
      }

      const parts = ctx.get('authorization').trim().split(' ')

      // 请求头没有登录凭证
      if (parts.length !== 2) {
        ctx.throw(
          new httpError.UnauthorizedError(APP_MESSAGES.AUTHORIZATION_NOT_EXIST)
        )
      }

      const [scheme, token] = parts

      if (/^Bearer$/i.test(scheme)) {
        try {
          const res = await jwtService.verify(token, { complete: true })

          if (typeof res !== 'string') {
            const key = `${CACHE_KEYS.LOGIN_TOKEN_KEY}${res.payload.sub}`
            const onlineInfo = await cacheManager.get<any>(key)

            if (!onlineInfo) {
              ctx.throw(
                new httpError.UnauthorizedError(
                  APP_MESSAGES.AUTHORIZATION_NOT_CORRECT
                )
              )
            }

            stateService.setJwtPayload(res.payload)
          }
        } catch {
          // 退出接口忽略该错误
          if (!ctx.path.includes('/auth/logout')) {
            ctx.throw(
              new httpError.UnauthorizedError(
                APP_MESSAGES.AUTHORIZATION_EXPIRED
              )
            )
          }
        }
      } else {
        ctx.throw(
          new httpError.UnauthorizedError(
            APP_MESSAGES.AUTHORIZATION_NOT_CORRECT
          )
        )
      }

      await next()
    }
  }

  match(ctx: Context) {
    const isApi = ctx.path.startsWith('/api')
    const ignorePath =
      ['/auth/captcha', '/auth/login'].findIndex(v => ctx.path.includes(v)) ===
      -1

    return isApi && ignorePath
  }
}
